package cn.blnp.net.flowable.task.ext.camel;

import cn.blnp.net.flowable.consumer.ResultProcessor;
import cn.hutool.core.map.MapUtil;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.flowable.variable.api.history.HistoricVariableInstance;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <h3>Camel 任务案例</h3>
 *
 * @author <a href="mailto:blnp.yibin@qq.com">lyb</a>
 * @version 1.0
 * @since 2025/5/14 9:47
 */
@Slf4j
@SpringBootTest
public class CamelTaskTest {

    @Resource
    private RepositoryService repositoryService;
    @Resource
    private RuntimeService runtimeService;
    @Resource
    private TaskService taskService;
    @Resource
    private HistoryService historyService;

    /**
     *  <h2>Camel任务</h2>
     *  <h3>认识Camel</h3>
     *  <hr/>
     *  <p>
     *   &emsp;&emsp;Camel是Apache基金会下的一个开源项目，是一款集成项目利器，针对应用集成场景抽象出了一套消息交互模型，通过组件的方式进行第三方系统的接入。目前Camel已经提供了300多种组件，
     *   支持HTTP、JMS、TCP、WebSocket等多种传输协议。Camel结合企业应用集成模式的特点提供了消息路由、消息转换等领域特定语言（Domain-Specific Language，DSL），极大地降低了集成应用的开发难度。
     *  </p><br/>
     *  <h3>图形标记</h3>
     *  <p>
     *   &emsp;&emsp;因为Camel任务并非BPMN 2.0规范的“官方”任务，所以没有提供其专用图标。在Flowable中，Camel任务表示为左上角带有骆驼图标的圆角矩形，如图所示：<br/>
     *   <img src="https://cdn4.winhlb.com/2025/05/14/6823f7a9c43e0.png" />
     *  </p><br/>
     *  <h3>定义格式</h3>
     *  <p>
     *   <pre>
     *       {@code
     *          <serviceTask id="camelTask1" name="Camel任务" flowable:type="camel" />
     *       }
     *   </pre>
     *  </p><br/>
     *  <h3>集成Camel</h3>
     *  <ol>
     *      <li>
     *          <a href="https://blog.csdn.net/qq_18244417/article/details/147081581" >SpringBoot集成</a>
     *      </li>
     *      <li>
     *          <a href="https://www.yisu.com/jc/923633.html">参考文章2</a>
     *      </li>
     *  </ol>
     **/
    @Test
    public void testBase() {

    }

    /**
     *  <h2>Flowable路由参数</h2>
     *  <hr/>
     *  <h3>输入参数</h3>
     *  <p>
     *      Flowable提供了3种输入参数，用于控制将流程变量复制到Camel的策略：<br/>
     *      <ol>
     *          <li>
     *              <b>copyVariablesToProperties：</b>
     *              <ul>
     *                  <li>对应Flowable行为类：</li> {@link org.flowable.camel.impl.CamelBehaviorDefaultImpl}
     *                  <li>描述：</li>默认配置，将Flowable的流程变量复制为Camel参数，在路由中可以通过形如${property.variableName}的表达式获取参数值
     *              </ul>
     *          </li>
     *          <li>
     *              <b>copyCamelBodyToBody：</b>
     *              <ul>
     *                  <li>对应Flowable行为类：</li> {@link org.flowable.camel.impl.CamelBehaviorCamelBodyImpl}
     *                  <li>描述：</li>只将名为"camelBody"的Flowable流程变量复制为Camel消息体。如果camelBody的值是Map对象，在路由中可以通过形如${body[variableName]}的表达式获取参数值；如果camelBody的值是
     *                  纯字符，可以通过${body}表达式获取
     *              </ul>
     *          </li>
     *          <li>
     *              <b>copyVariablesToBodyAsMap：</b>
     *              <ul>
     *                  <li>对应Flowable行为类：</li> {@link org.flowable.camel.impl.CamelBehaviorBodyAsMapImpl}
     *                  <li>描述：</li>把Flowable的所有流程变量复制到一个Map对象中，作为Camel的消息体，在路由中可以通过形如${body [variableName]}的表达式获取参数值
     *              </ul>
     *          </li>
     *      </ol>
     *      路由规则配置示例：<br/>
     *  <pre>
     *      {@code
     *          from("flowable:CamelTaskProcess:camelTask1?copyVariablesToProperties=true")
     *              .to("log:org.flowable.camel.examples.SimpleCamelCall");
     *      }
     *  </pre>
     *  </p><br/>
     *  <h3>输出参数</h3>
     *  <p>
     *      <ol>
     *          <li>
     *              <b>default：</b>默认配置。如果Camel消息体是一个Map对象，则在路由执行结束后将其中每一个属性复制为Flowable的流程变量，否则将整个Camel消息体复制到名为“camelBody”的流程变量中
     *          </li>
     *          <li>
     *              <b>copyVariablesFromProperties：</b>将Camel参数以相同的名称复制为Flowable流程变量
     *          </li>
     *          <li>
     *              <b>copyVariablesFromHeader：</b>将Camel Header中的内容以相同的名称复制为Flowable流程变量
     *          </li>
     *          <li>
     *              <b>copyCamelBodyToBodyAsString：</b>与default相同，但如果Camel消息体并非Map对象，则先将其转换为字符串，然后复制到名为camelBody的流程变量中
     *          </li>
     *      </ol>
     *      路由规则配置示例：<br/>
     *  <pre>
     *      {@code
     *          from("flowable:CamelTaskProcess:camelTask1?copyVariablesFromProperties=true")
     *              .to("log:org.flowable.camel.examples.SimpleCamelCall");
     *      }
     *  </pre>
     *  </p>
     **/
    @Test
    public void testRoute() {

    }

    /**
     *  <h2>异步调用</h2>
     *  <p>
     *      默认情况下，Camel任务是同步执行的。流程执行到Camel任务后将处于等待状态，直到Camel执行结束并返回结果，才会离开Camel任务继续流转。如果Camel任务执行时间较长，
     *  或者某些场景下不需要同步执行，则可以使用Camel任务的异步功能实现，只需将Camel任务的async参数设置为true：
     *  <pre>
     *      {@code
     *          <serviceTask id="camelTask1" name="异步Camel任务" flowable:type="camel" flowable:async="true"/>
     *      }
     *  </pre>
     *  </p>
     **/
    @Test
    public void testAsync() {

    }

    /**
     *  <h2>启动流程</h2>
     *  <p>
     *      首先启动Flowable流程实例，然后在流程实例中启动Camel路由。对应地，也可以通过Camel任务启动或调用流程实例，Camel路由规则可以设计如下：
     *  <pre>
     *      {@code
     *          from("flowable:ParentProcess:camelTaskForStartSubprocess")
     *              .to("flowable:SubProcessCreateByCamel");
     *      }
     *  </pre>
     *  </p>
     **/
    @Test
    public void testStartFlow() {

    }

    /**
     *  <h2>流程案例演示</h2>
     *  <p>
     *      该流程为调用外部第三方服务自动获取IP信息的流程。流程发起后，先通过用户任务提交数据，然后通过Camel任务调用外部Web服务查询IP信息。<br/>
     *      <b>流程定义文件：</b>{@link flow/camelTaskProcess.bpmn20.xml}<br/>
     *      <b>流程预览图：</b><br/>
     *      <img src="https://cdn4.winhlb.com/2025/05/14/682412fcead9b.png" /><br/>
     *
     *   路由规则配置如下所示：
     *   <pre>
     *       {@code
     *          from("flowable:camelTaskProcess:camelTask1?copyVariablesToProperties=true")
     *                 .toD("http://whois.pconline.com.cn/ipJson.jsp?ip=${exchange.properties[ip]}&json=true")
     *                 .process(new ResultProcessor())
     *       }
     *   </pre>
     *   规则配置详见：{@link cn.blnp.net.flowable.config.MyCamelRoutes}<br/>
     *   process 详见：{@link ResultProcessor}
     *  </p>
     *  <h3>路由规则说明</h3>
     *  <p>
     *      该Java类重写了org.apache.camel.builder.RouteBuilder类的configure()方法，并在该方法中实现了路由逻辑。from声明的起始端点的URI中，flowable为协议头，
     *  camelTaskProcess为流程定义key，camelTask1为Camel任务ID，输入参数配置的copyVariablesToProperties=true表示将Flowable的流程变量复制为Camel参数，输出参数使
     *  用默认配置。
     *  </p>
     *  <p>
     *      终止端点采用toD声明，它允许通过表达式的方式来动态定义消息的接收节点，这里使用表达式${exchange. properties[ip]}，表示从Camel参数中获取ip属性值。
     *  </p>
     *  <p>
     *      路由中使用了自定义Processor处理器ResultProcessor。Processor处理器是Camel中的一个重要元素，用于接收从控制端点、路由选择条件或另一个处理器的Exchange中传来的消息信息，并进行处理。
     *  通过自定义的Processor处理器可以编写一些业务逻辑处理，如用于实现外部服务返回结果数据格式的转换，代码如下：
     *  </p>
     **/
    @Test
    public void testExample() {
        Deployment deploy = repositoryService.createDeployment()
                .name("Camel 任务整合案例")
                .addClasspathResource("flow/camelTaskProcess.bpmn20.xml")
                .deploy();
        log.info("部署流程定义：{}", deploy.getId());
        ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery()
                .deploymentId(deploy.getId())
                .singleResult();
        log.info("流程定义编号：{}", procDef.getId());
        //启动流程实例
        ProcessInstance procInst = runtimeService.startProcessInstanceById(procDef.getId());
        TaskQuery taskQuery = taskService.createTaskQuery()
                .processInstanceId(procInst.getId());

        //查询并完成第一个任务
        Task firstTask = taskQuery.singleResult();
        //设置流程变量
        Map<String, Object> varMap = MapUtil.builder(new HashMap<String, Object>())
                .put("ip", "119.99.130.0")
                .build();
        taskService.complete(firstTask.getId(), varMap);

        //查询并打印流程变量
        List<HistoricVariableInstance> hisVars = historyService.createHistoricVariableInstanceQuery()
                .processInstanceId(procInst.getId())
                .list();
        hisVars.stream()
                .forEach((hisVar) -> log.info("流程变量名：{}，变量值：{}",
                hisVar.getVariableName(), JSON.toJSONString(hisVar.getValue())));

        /**
         *  演示结果：
         *  2025-05-14 13:42:03.174  INFO 85896 --- [           main] c.b.n.f.task.ext.camel.CamelTaskTest     : 部署流程定义：292a632d-3086-11f0-84c5-50ebf6ec8f6d
         * 2025-05-14 13:42:03.180  INFO 85896 --- [           main] c.b.n.f.task.ext.camel.CamelTaskTest     : 流程定义编号：camelTaskProcess:1:29ac1650-3086-11f0-84c5-50ebf6ec8f6d
         * 2025-05-14 13:42:04.776  INFO 85896 --- [           main] c.b.n.flowable.consumer.ResultProcessor  : Camel调用结果为：
         *
         *
         *
         *
         * {"ip":"119.99.130.0","pro":"湖北省","proCode":"420000","city":"孝感市","cityCode":"420900","region":"","regionCode":"0","addr":"湖北省孝感市 电信","regionNames":"","err":""}
         *
         *
         *
         *
         * 2025-05-14 13:42:05.162  INFO 85896 --- [           main] c.b.n.f.task.ext.camel.CamelTaskTest     : 流程变量名：ip，变量值："119.99.130.0"
         * 2025-05-14 13:42:05.162  INFO 85896 --- [           main] c.b.n.f.task.ext.camel.CamelTaskTest     : 流程变量名：city，变量值："孝感市"
         * 2025-05-14 13:42:05.162  INFO 85896 --- [           main] c.b.n.f.task.ext.camel.CamelTaskTest     : 流程变量名：addr，变量值："湖北省孝感市 电信"
         * 2025-05-14 13:42:05.162  INFO 85896 --- [           main] c.b.n.f.task.ext.camel.CamelTaskTest     : 流程变量名：pro，变量值："湖北省"
         **/
    }
}
